﻿#region License and Copyright Notice

//XOML: XML based object persistence framework

//Copyright (c) 2008 Ananth B.
//All rights reserved.

//The contents of this file are made available under the terms of the
//Eclipse Public License v1.0 (the "License") which accompanies this
//distribution, and is available at the following URL:
//http://www.opensource.org/licenses/eclipse-1.0.php

//Software distributed under the License is distributed on an "AS IS" basis,
//WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
//the specific language governing rights and limitations under the License.

//By using this software in any fashion, you are agreeing to be bound by the
//terms of the License.

#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace Xoml
{
    public static class XomlLoader
    {
        private static object _root;
        
        private struct DeferInformation // Contains information about any markup extensions that require deferred processing
        {
            private object _context;
            private PropertyInfo _property;
            private XmlNode _node;

            public DeferInformation(object context, PropertyInfo property, XmlNode node)
            {
                if (context == null)
                    throw new ArgumentNullException("context");
                if (property == null)
                    throw new ArgumentNullException("property");
                if (node == null)
                    throw new ArgumentNullException("node");

                _context = context;
                _property = property;
                _node = node;
            }

            public object Context
            {
                get
                {
                    return _context;
                }
            }

            public PropertyInfo Property
            {
                get
                {
                    return _property;
                }
            }

            public XmlNode Node
            {
                get
                {
                    return _node;
                }
            }
        }
        
        private static readonly Regex _explicitReference = new Regex(@"^(clr-namespace\s*:\s*(?<clrnamespace>.+));(assembly\s*=\s*(?<assemblyname>.+))$");
        private static readonly Regex _elementDeclaration = new Regex(@"^(?<prefix>\w+:)?(?<type>\w+)(\.(?<property>\w+))?$");
        private static readonly Regex _xmlnsDeclaration = new Regex(@"^(xmlns)(:\w+)?");
        private static readonly Regex _markupExtension = new Regex(@"^{.+}$");
        
        private static List<string> _loadedAssemblies = new List<string>();
        private static Dictionary<string, List<string>> _namespaceMappings = new Dictionary<string, List<string>>();
        private static Dictionary<string, string> _assemblyNameMappings = new Dictionary<string, string>();

        private static List<Type> _markupExtensions = new List<Type>();
        private static List<DeferInformation> _deferredExtensions = new List<DeferInformation>();

        private static void RefreshCache()
        {
            _namespaceMappings.Clear();
            _assemblyNameMappings.Clear();
            _loadedAssemblies.Clear();

            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                // Add only the assembly name part, since thats what we're going to use.
                _loadedAssemblies.Add(assembly.FullName.Split(',')[0]);

                // Add Xml namespace to CLR namespace mappings for all assemblies.
                XmlnsDefinitionAttribute[] nsDefinitions = assembly.GetCustomAttributes(typeof(XmlnsDefinitionAttribute), false) as XmlnsDefinitionAttribute[];
                foreach (XmlnsDefinitionAttribute nsDefinition in nsDefinitions)
                {
                    if (!_assemblyNameMappings.ContainsKey(nsDefinition.XmlNamespace))
                        _assemblyNameMappings.Add(nsDefinition.XmlNamespace, nsDefinition.AssemblyName); // Xml namespace versus the assembly name.

                    if (!_namespaceMappings.ContainsKey(nsDefinition.XmlNamespace))
                        _namespaceMappings.Add(nsDefinition.XmlNamespace, new List<string>());
                    _namespaceMappings[nsDefinition.XmlNamespace].Add(nsDefinition.ClrNamespace); // Xml namespace versus all the CLR namespaces.
                }
            }
        }
        
        private static bool IsExplicitReference(string ns)
        {
            return _explicitReference.Match(ns).Success;
        }

        private static bool IsPropertyDeclaration(string declaration)
        {
            Match match = _elementDeclaration.Match(declaration);
            return match.Groups["type"].Success && match.Groups["property"].Success;
        }

        private static void CheckNamespaceAssemblies(string ns)
        {
            if (IsExplicitReference(ns))
            {
                Match match = _explicitReference.Match(ns);
                string clrNamespace = match.Groups["clrnamespace"].Value;

                // Remember the user asked for this namespace. Create a fake Xml namespace for it, by its prefix.
                if (!_namespaceMappings.ContainsKey(ns))
                    _namespaceMappings[ns] = new List<string>();
                if (!_namespaceMappings[ns].Contains(clrNamespace))
                    _namespaceMappings[ns].Add(clrNamespace); 

                string assemblyName = match.Groups["assemblyname"].Value;
                if (!_loadedAssemblies.Contains(assemblyName))
                    Assembly.Load(assemblyName); // Should we load this at all? Or is it upto the user to make sure all required assemblies are loaded into the AppDomain?

                if (!_assemblyNameMappings.ContainsKey(ns))
                    _assemblyNameMappings.Add(ns, assemblyName); // Xml namespace versus the assembly name.
            }
            else
            {
                // Not an explicit reference
                if (!_namespaceMappings.ContainsKey(ns))
                    throw new NamespaceNotFoundException(ns);

                foreach (string clrNamespace in _namespaceMappings[ns])
                    if (!_namespaceMappings[ns].Contains(clrNamespace)) // Make sure there are no duplicates under the same Xml namespace.
                        _namespaceMappings[ns].Add(clrNamespace);
                // TODO: It is possible that the same CLR namespace maps to multiple Xml namespaces at this time! Resolve this, or treat it as a "feature".
            }
        }

        private static Type FindType(string typeName, string ns)
        {
            return FindType(typeName, ns, true);
        }

        private static object ConvertTo(object value, Type destinationType)
        {
            return ConvertTo(value, destinationType, CultureInfo.InvariantCulture);
        }

        private static object ConvertTo(object value, Type destinationType, CultureInfo culture)
        {
            if (value == null)
                if (destinationType.IsValueType)
                    throw new InvalidCastException(string.Format(CultureInfo.InvariantCulture, "Cannot convert null to value type {0}", destinationType.Name));
                else
                    return value;
            
            if (destinationType == typeof(object)) // Since this is so common, we're going to allow this out of the box
                return (object)value;

            IConvertible convertible = value as IConvertible; // If the object implements IConvertible, use it
            if (convertible != null)
                return convertible.ToType(destinationType, culture);
            
            Type sourceType = value.GetType(); // Otherwise get its TypeConverter
            TypeConverter converter = TypeDescriptor.GetConverter(value.GetType(), true);
            if (converter == null)
                throw new TypeConversionException(string.Format(CultureInfo.InvariantCulture, "No TypeConverter is defined for {0}.", sourceType.Name));

            if (converter.CanConvertTo(destinationType))
                return converter.ConvertTo(null, culture, value, destinationType); // TODO: Do we need to add a non-null ITypeDescriptorContext here?
            else
                throw new TypeConversionException(string.Format(CultureInfo.InvariantCulture, "{0} does not know how to convert {1} to {2}", converter.GetType().Name, sourceType.Name, destinationType.Name));
        }

        private static Type FindType(string typeName, string ns, bool isExtension)
        {
            foreach (string clrNamespace in _namespaceMappings[ns])
            {
                // Since we know only the types' name, try it with all CLR namespaces we know are mapped to this XmlElements' prefix.
                // TODO: Support generics?
                Type type = Type.GetType(clrNamespace + "." + typeName + "," + _assemblyNameMappings[ns]); // Assembly qualified name, we hope.
                if ((type == null) && isExtension) // If we didnt find it ...
                    type = Type.GetType(clrNamespace + "." + typeName + "Extension" + "," + _assemblyNameMappings[ns]); // ... try again, with "Extension" tagged onto the type's name

                if (type != null)
                {
                    if (typeof(MarkupExtension).IsAssignableFrom(type)) // Is this a markup extension?
                        if (!_markupExtensions.Contains(type)) // Do we have it already?
                            _markupExtensions.Add(type); // If not, note it down. We need to call it's AfterLoad later
                    
                    return type;
                }
            }

            return null;
        }

        // This is called to begin setting up a markup extension expression (perhaps nested) for evaluation
        private static object ProcessMarkupExtensions(XmlDocument document, object instance, PropertyInfo property, string value, out bool defer)
        {
            defer = false; // This is false unless someone says otherwise
            // Just check if it begins with a { and end with a }
            Match match = _markupExtension.Match(value);
            if (!match.Success)
                return ConvertTo(value, property.PropertyType); // Not a markup extension, convert its type properly and return.

            // Parse it properly now
            StringTokenizer tokenizer = new StringTokenizer(value);
            tokenizer.QuoteChar = '\'';
            tokenizer.IgnoreWhiteSpace = true;
            tokenizer.Next();
            Node node = MarkupExtensionParser.Parse(tokenizer); // This might throw a variety of InvalidXomlExceptions, dont catch them.
            return ProcessMarkupExtensions(document, instance, property, node, out defer);
        }

        // This routine is for processing markup extensions defined in attribute form
        private static object ProcessMarkupExtensions(XmlDocument document, object instance, PropertyInfo property, Node node, out bool defer)
        {
            defer = false; // This is false unless someone says otherwise
            if (!(node is MarkupExtensionNode)) // We found something else, return its value
                return node.Value;
            MarkupExtensionNode markupExtensionNode = node as MarkupExtensionNode;

            // Find out what markup extension it is, use the prefix to resolve it
            Type markupExtensionType = FindType(markupExtensionNode.ExtensionName, document.DocumentElement.GetNamespaceOfPrefix(markupExtensionNode.Prefix), true);
            if (markupExtensionType == null)
                throw new TypeNotFoundException(markupExtensionNode.ExtensionName); // We didn't find this type.
            MarkupExtension extension = Activator.CreateInstance(markupExtensionType) as MarkupExtension; // Call it's parameterless constructor

            foreach (KeyValuePair<string, Node> argument in markupExtensionNode.Arguments)
            {
                PropertyInfo currentProperty = null;
                if (argument.Key == string.Empty) // Default argument
                {
                    // Is it defined on this markup extension?
                    if (extension.DefaultArgument == null)
                        throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "The markup extension {0} does not have a default argument defined. Please check its usage", markupExtensionType.Name));

                    // It's defined, save it
                    currentProperty = extension.DefaultArgument;
                }
                else
                    currentProperty = extension.GetProperty(argument.Key); // explicit property

                if (argument.Value is MarkupExtensionNode) // Set up this markup extension first
                {
                    object value = ProcessMarkupExtensions(document, extension, currentProperty, argument.Value, out defer);

                    if (!defer)
                        currentProperty.SetValue(extension, ConvertTo(value, currentProperty.PropertyType), null); // We got a value
                    else
                        return value; // Defer
                }
                else
                    currentProperty.SetValue(extension, ConvertTo(argument.Value.Value, currentProperty.PropertyType), null); // This is just a literal, change its type and set
            }

            return extension.ProvideValue(instance, property, out defer);
        }

        // This is called to begin setting up a deferred markup extension expression (perhaps nested) for evaluation
        private static object ProcessDeferredMarkupExtensions(XmlDocument document, object instance, PropertyInfo property, string value)
        {
            // Just check if it begins with a { and end with a }
            Match match = _markupExtension.Match(value);
            if (!match.Success)
                return ConvertTo(value, property.PropertyType); // Not a markup extension, convert its type properly and return.

            // Parse it properly now
            StringTokenizer tokenizer = new StringTokenizer(value);
            tokenizer.QuoteChar = '\'';
            tokenizer.IgnoreWhiteSpace = true;
            tokenizer.Next();
            Node node = MarkupExtensionParser.Parse(tokenizer); // This might throw a variety of InvalidXomlExceptions, dont catch them.
            return ProcessDeferredMarkupExtensions(document, instance, property, node);
        }

        private static object ProcessDeferredMarkupExtensions(XmlDocument document, object instance, PropertyInfo property, Node node)
        {
            if (!(node is MarkupExtensionNode)) // We found something else, return its value
                return node.Value;
            MarkupExtensionNode markupExtensionNode = node as MarkupExtensionNode;

            // Find out what markup extension it is, use the prefix to resolve it
            Type markupExtensionType = FindType(markupExtensionNode.ExtensionName, document.DocumentElement.GetNamespaceOfPrefix(markupExtensionNode.Prefix), true);
            if (markupExtensionType == null)
                throw new TypeNotFoundException(markupExtensionNode.ExtensionName); // We didn't find this type.
            MarkupExtension extension = Activator.CreateInstance(markupExtensionType) as MarkupExtension; // Call it's parameterless constructor

            foreach (KeyValuePair<string, Node> argument in markupExtensionNode.Arguments)
            {
                PropertyInfo currentProperty = null;
                if (argument.Key == string.Empty) // Default argument
                {
                    // Is it defined on this markup extension?
                    if (extension.DefaultArgument == null)
                        throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "The markup extension {0} does not have a default argument defined. Please check its usage", markupExtensionType.Name));

                    // It's defined, save it
                    currentProperty = extension.DefaultArgument;
                }
                else
                    currentProperty = extension.GetProperty(argument.Key);

                if (argument.Value is MarkupExtensionNode) // Set up this markup extension first
                {
                    object value = ProcessDeferredMarkupExtensions(document, extension, currentProperty, argument.Value);
                    currentProperty.SetValue(extension, ConvertTo(value, currentProperty.PropertyType), null);
                }
                else
                    currentProperty.SetValue(extension, ConvertTo(argument.Value.Value, currentProperty.PropertyType), null); // This is just a literal, change it's type and set it
            }

            return extension.DeferredProvideValue(_root, instance, property);
        }

        // Called by default for non-deferred GetObject
        private static object GetObject(XmlDocument document, object current, XmlNode node)
        {
            return GetObject(document, current, node, false);
        }

        private static object GetObject(XmlDocument document, object current, XmlNode node, bool isDeferred)
        {
            switch (node.NodeType)
            {
                case XmlNodeType.Text:
                    return node.Value; // Just return its value. Can this ever be a markup extension?

                case XmlNodeType.Attribute:
                    if (isDeferred)
                        return ProcessDeferredMarkupExtensions(document, current, current.GetType().GetProperty(node.Name), node.Value); // Set this attribute up for recursive processing
                    // Can we ever get a non-deferred attribute for processing?
                    break;

                case XmlNodeType.Element:
                    {
                        Match match = _elementDeclaration.Match(node.Name);

                        // Is this a property declaration?
                        if (match.Groups["type"].Success && match.Groups["property"].Success)
                        {
                            // Yes, it is
                            if (current == null) // Make sure we have a valid context.
                                throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "The node {0} is invalid in this context. <Type>.<Property> nodes can only be specified inside an object definition", node.Name));

                            PropertyInfo property = current.GetType().GetProperty(match.Groups["property"].Value);
                            if (property == null)
                                throw new PropertyNotFoundException(match.Groups["property"].Value, current.GetType());
                            IList list = property.GetValue(current, null) as IList;
                            IDictionary dictionary = property.GetValue(current, null) as IDictionary;
                            
                            if ((node.ChildNodes.Count >= 1) &&
                                ((list != null) || (dictionary != null))) // If we have one or more nodes under it, and this properties value is an IList or IDictionary, we add items to it
                            {
                                // There are multiple child nodes under it, so this property must be a list or dictionary of some sort.
                                foreach (XmlNode child in node.ChildNodes)
                                {
                                    object childObject = GetObject(document, current, child);
                                    object result = childObject;
                                    if (typeof(MarkupExtension).IsAssignableFrom(childObject.GetType())) // Is this a markup extension?
                                    {
                                        if (isDeferred)
                                            result = (childObject as MarkupExtension).DeferredProvideValue(_root, current, null); // if it is, called DeferredProvideValue
                                        else
                                        {
                                            bool defer = false;
                                            result = (childObject as MarkupExtension).ProvideValue(current, null, out defer); // if it is, call its ProvideValue
                                            if (defer) // Deferred?
                                            {
                                                _deferredExtensions.Add(new DeferInformation(current, null, child)); // If the extension asked to be deferred, remember it
                                                continue;
                                            }
                                        }
                                    }
                                    
                                    if (list != null) // Is it a List?
                                        list.Add(result); // Add it to the list
                                    else if (dictionary != null) // Is it a Dictionary?
                                    {
                                        try
                                        {
                                            DictionaryEntry entry = (DictionaryEntry)result;
                                            dictionary.Add(entry.Key, entry.Value); // Add it to the dictionary
                                        }
                                        catch (InvalidCastException e)
                                        {
                                            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Only DictionaryEntry instances can be added to {0}", node.Name), e);
                                        }
                                    }
                                }
                            }
                            else // Otherwise it's just a property
                            {
                                if (property.CanWrite) // Is it settable?
                                {
                                    object child = GetObject(document, current, node.ChildNodes[0]); // Get the object declared in the child node
                                    object result = child;
                                    bool defer = false;
                                    if (typeof(MarkupExtension).IsAssignableFrom(child.GetType()))  // Is this a markup extension?
                                    {
                                        if (isDeferred) // Defer?
                                            result = (child as MarkupExtension).DeferredProvideValue(_root, current, property);
                                        else
                                        {
                                            result = (child as MarkupExtension).ProvideValue(current, property, out defer); // If it is, call its ProvideValue
                                            if (defer)
                                                _deferredExtensions.Add(new DeferInformation(current, property, node.ChildNodes[0])); // If the extension asked to be deferred, remember it
                                        }
                                    }

                                    property.SetValue(current, ConvertTo(result, property.PropertyType), null); // Set the (default) value in either case (i.e. deferred or not)
                                }
                                else
                                {
                                    // Try getting its value, and see if it is null. If it's not, go on. If it is, we can't proceed!
                                    object value = property.GetValue(current, null);
                                    if (value == null)
                                        throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Property {0} on {1} is read-only, and returns null.", property.Name, property.DeclaringType.Name));
                                    // don't change current, we're just a property setter
                                }
                            }
                        }

                        // Is this an object definition?
                        if (match.Groups["type"].Success && !match.Groups["property"].Success)
                        {
                            CheckNamespaceAssemblies(document.DocumentElement.GetNamespaceOfPrefix(node.Prefix));

                            Type objectType = FindType(match.Groups["type"].Value, document.DocumentElement.GetNamespaceOfPrefix(node.Prefix));
                            if (objectType == null)
                                throw new TypeNotFoundException(node.Name);

                            current = Activator.CreateInstance(objectType); // Create the object.

                            foreach (XmlAttribute attribute in node.Attributes) // Set all its attributes. Properties in the alternate syntax need to be handled too.
                            {
                                if (_xmlnsDeclaration.Match(attribute.Name).Success) // Skip all xmlns attributes, these will never be properties on the object.
                                    continue;

                                PropertyInfo property = objectType.GetProperty(attribute.Name);
                                if (property == null)
                                    throw new PropertyNotFoundException(attribute.Name, objectType);

                                if (isDeferred) // Is this a deferred call?
                                {
                                    object value = ProcessDeferredMarkupExtensions(document, current, property, attribute.Value); // Don't call ProcessMarkupExtensions, call ProcessDeferredMarkupExtensions
                                    property.SetValue(current, ConvertTo(value, property.PropertyType), null); // We must've got a value, set it
                                }
                                else
                                {
                                    bool defer;
                                    object value = ProcessMarkupExtensions(document, current, property, attribute.Value, out defer);
                                    if (!defer) // Defer?
                                        property.SetValue(current, ConvertTo(value, property.PropertyType), null);
                                    else
                                    {
                                        property.SetValue(current, ConvertTo(value, property.PropertyType), null); // Set the default values returned anyway
                                        _deferredExtensions.Add(new DeferInformation(current, property, attribute));
                                    }
                                }
                            }

                            IList list = current as IList; // Is this a list?
                            IDictionary dictionary = current as IDictionary; // Or a dictionary?

                            // Process all children
                            foreach (XmlNode child in node.ChildNodes)
                            {
                                if (!IsPropertyDeclaration(child.Name))
                                {
                                    object childObject = GetObject(document, current, child);
                                    object result = childObject;
                                    if (typeof(MarkupExtension).IsAssignableFrom(childObject.GetType())) // If this is a markup extension ...
                                    {
                                        if (isDeferred) // Is this a deferred call?
                                            result = (childObject as MarkupExtension).DeferredProvideValue(_root, current, null); // Call DeferredProvideValue, not ProvideValue
                                        else
                                        {
                                            bool defer;
                                            result = (childObject as MarkupExtension).ProvideValue(current, null, out defer); // ... call its ProvideValue
                                            if (defer)
                                                _deferredExtensions.Add(new DeferInformation(current, null, child));
                                        }
                                    }

                                    // First try to assign lone children to the default property, if defined
                                    if ((objectType.IsDefined(typeof(DefaultPropertyAttribute), false)) && (node.ChildNodes.Count == 1)) // We have exactly one child node, and a default property is defined
                                    {
                                        string propertyName = (objectType.GetCustomAttributes(typeof(DefaultPropertyAttribute), false)[0] as DefaultPropertyAttribute).Name;
                                        PropertyInfo property = objectType.GetProperty(propertyName);
                                        if (property == null)
                                            throw new PropertyNotFoundException(propertyName, objectType);

                                        property.SetValue(current, ConvertTo(result, property.PropertyType), null);
                                    }
                                    else // Either we don't have a default property, or we have multiple children
                                    {
                                        // See if this is a list, so we can add elements directly into it
                                        if ((list == null) && (dictionary == null))
                                            throw new InvalidXomlException(string.Format(CultureInfo.InvariantCulture, "Cannot directly add element {0} to a non-list type {1}", child.Name, node.Name));

                                        if (list != null)
                                            list.Add(result); // This is a list, add any contained declarations to it.
                                        else if (dictionary != null) // Is this a dictionary?
                                        {
                                            try
                                            {
                                                DictionaryEntry entry = (DictionaryEntry)result;
                                                dictionary.Add(entry.Key, entry.Value); // Add it to the dictionary
                                            }
                                            catch (InvalidCastException e)
                                            {
                                                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Only DictionaryEntry instances can be added to {0}", node.Name), e);
                                            }
                                        }
                                    }
                                }
                                else
                                    GetObject(document, current, child); // This is just a property set. GetObject will return the object reference anyway, we don't need to save it.
                            }
                        }
                    }
                    return current;
                }

                // TODO: Remove this later ... :)
                throw new InvalidXomlException("Unknown error");
            }
                
        // TODO: Add a parameter to pass in additional namespace mappings.
        public static object Load(Stream stream)
        {
            RefreshCache(); // Cache the list of assemblies and types loaded into this AppDomain.
            
            XmlDocument document = new XmlDocument();
            document.Load(stream);

            _markupExtensions.Clear(); // Clear the list of markup extensions found on the last Load
            _deferredExtensions.Clear(); // Clear the list of markup extensions that wished to be deferred on the last Load

            // Process the root element
            object result = GetObject(document, null, document.DocumentElement);

            if (result == null) // Return the result, we can't do anything with null
                return result;

            bool defer = false;
            if (typeof(MarkupExtension).IsAssignableFrom(result.GetType())) // If the root was a markup extension, call its ProvideValue
                result = (result as MarkupExtension).ProvideValue(result, null, out defer);

            if (defer) // We can't defer if a markup extension is applied to the root
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Cannot defer ProvideValue on {0} since it is defined at the root of the Xoml hierarchy", result.GetType().Name));

            _root = result; // Copy a reference to the root object
            // Call all markup extensions that wished to be deferred
            foreach (DeferInformation info in _deferredExtensions)
            {
                object value = GetObject(document, info.Context, info.Node, true); // Get the new value, make sure you indicate this is a deferred call
                
                if (value != null)
                    if (typeof(MarkupExtension).IsAssignableFrom(value.GetType()))  // Is this a markup extension?
                        value = (value as MarkupExtension).DeferredProvideValue(_root, value, info.Property);

                info.Property.SetValue(info.Context, ConvertTo(value, info.Property.PropertyType), null); // Set the value
            }
            _root = null; // Get rid of this reference

            // Call AfterLoad on every extension we've found so far
            foreach (Type type in _markupExtensions)
            {
                AfterLoadEventArgs e = new AfterLoadEventArgs(result); // Set up EventArgs
                MarkupExtension m = Activator.CreateInstance(type, new object[] { }) as MarkupExtension;
                m.OnAfterLoad(e);
                result = e.Root; // Take the new(?) root object it provided
            } // Process the next one

            return result; // Return (new?) root object
        }

        public static object Load(string xoml)
        {
            return Load(new MemoryStream(new ASCIIEncoding().GetBytes(xoml)));
        }
    }

    [global::System.Serializable]
    public class InvalidXomlException : Exception
    {
        public InvalidXomlException(string message) : base(message)
        {
        }

        public InvalidXomlException(string message, Exception inner) : base(message, inner)
        {
        }

        protected InvalidXomlException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }

    [global::System.Serializable]
    public class TypeConversionException : Exception
    {
        public TypeConversionException(string message)
            : base(message)
        {
        }

        public TypeConversionException(string message, Exception inner)
            : base(message, inner)
        {
        }

        protected TypeConversionException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }

    [global::System.Serializable]
    public class TypeNotFoundException : Exception
    {
        public TypeNotFoundException(string type)
            : base(string.Format(CultureInfo.InvariantCulture, "The type \"{0}\"was not found. Please check if the type exists in the referenced namespace, or has been misspelt in the Xoml", type))
        {
        }
        
        protected TypeNotFoundException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }

    [global::System.Serializable]
    public class NamespaceNotFoundException : Exception
    {
        public NamespaceNotFoundException(string ns)
            : base(string.Format(CultureInfo.InvariantCulture, "The namespace \"{0}\" was not found linked using the XmlnsDefinitionAttribute in any loaded assemblies.", ns))
        {
        }

        protected NamespaceNotFoundException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }

    [global::System.Serializable]
    public class PropertyNotFoundException : Exception
    {
        public PropertyNotFoundException(string propertyName, Type type)
            : base(string.Format(CultureInfo.InvariantCulture, "The property \"{0}\" was not found for the type \"{1}\"", propertyName, type))
        {
        }
        
        protected PropertyNotFoundException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
        }
    }
}